#include <QtGui>
#include "editscreen.h"
#include "codeeditor.h"
#include "highlighter.h"
#include "projectmanager.h"
#include "projectview.h"
#include "errorlist.h"

EditScreen::EditScreen(QWidget *parent) :
  QWidget(parent) {
  s = new QSettings;
  currentPath = new QDir();
  watcher = new QFileSystemWatcher(this);
  connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(ShowFileModifiedNode(QString)));
  QHBoxLayout* layout = new QHBoxLayout(this);
  this->setLayout(layout);
  QVBoxLayout* leftlayout = new QVBoxLayout();
  QVBoxLayout* mainlayout = new QVBoxLayout();
  layout->addLayout(leftlayout);
  layout->addLayout(mainlayout);

  QHBoxLayout* toollist = new QHBoxLayout();
  leftlayout->addLayout(toollist);
  leftlayout->setAlignment(toollist, Qt::AlignLeft);

  QPushButton* bt_format = new QPushButton();
  bt_format->setMaximumWidth(22);
  bt_format->setToolTip("Format");
  bt_format->setIcon(QIcon(":/icon/format.png"));
  connect(bt_format, SIGNAL(clicked()), this, SLOT(Format()));
  QPushButton* bt_build = new QPushButton();
  bt_build->setMaximumWidth(22);
  bt_build->setToolTip("Build");
  bt_build->setIcon(QIcon(":/icon/build.png"));
  connect(bt_build, SIGNAL(clicked()), this, SLOT(Build()));
  bt_run = new QPushButton();
  bt_run->setCheckable(true);
  bt_run->setMaximumWidth(22);
  bt_run->setToolTip("Run");
  bt_run->setIcon(QIcon(":/icon/run.png"));
  connect(bt_run, SIGNAL(clicked()), this, SLOT(Run()));
  QPushButton* bt_deploy = new QPushButton();
  bt_deploy->setMaximumWidth(22);
  bt_deploy->setToolTip("Deploy");
  bt_deploy->setIcon(QIcon(":/icon/upload.png"));

  toollist->addWidget(bt_format);
  toollist->addWidget(bt_build);
  toollist->addWidget(bt_run);
  toollist->addWidget(bt_deploy);
  //folder view
  tree = new ProjectView(this);
  leftlayout->addWidget(tree);
  connect(tree, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(ShowFileSelected(QModelIndex)));
  connect(tree, SIGNAL(addItemRequested()), this, SLOT(AddFile()));
  connect(tree, SIGNAL(removeRequested()), this, SLOT(RemoveFile()));
  connect(tree, SIGNAL(renameRequested()), this, SLOT(RenameFile()));
  tab = new QTabWidget();
  tab->setTabsClosable(true);
  connect(tab, SIGNAL(tabCloseRequested(int)), this, SLOT(CloseTab(int)));
  mainlayout->addWidget(tab);
  //out put tab (error, application output...)
  output_tab = new QTabWidget();
  output_tab->setMaximumHeight(300);
  output_tab->hide();
  mainlayout->addWidget(output_tab);

  errlist = new ErrorList();
  output_tab->addTab(errlist, "Issues");
  build_output = new QPlainTextEdit;
  build_output->setReadOnly(true);
  output_tab->addTab(build_output, "Build Output");
  run_output = new QPlainTextEdit;
  run_output->setReadOnly(true);
  output_tab->addTab(run_output, "Application Output");

  connect(errlist, SIGNAL(error_clicked(QString,int)), this,
          SLOT(JumpTo(QString,int)));

  //bottom tool area (output toogle, search..)
  QVBoxLayout* bottom_tools = new QVBoxLayout();
  mainlayout->addLayout(bottom_tools);

  bt_toggle_output = new QPushButton("Output");
  bt_toggle_output->setMaximumWidth(50);
  bt_toggle_output->setCheckable(true);
  connect(bt_toggle_output, SIGNAL(toggled(bool)), output_tab,
          SLOT(setVisible(bool)));
  bottom_tools->addWidget(bt_toggle_output);

  run_process = new QProcess;
  run_process->setProcessChannelMode(QProcess::MergedChannels);
  connect(run_process, SIGNAL(readyReadStandardOutput()), this, SLOT(ShowRunOutput()));
  connect(run_process, SIGNAL(finished(int)), this, SLOT(ResetRunButton(int)));
  build_process = new QProcess;
  connect(build_process, SIGNAL(finished(int)), this, SLOT(ShowBuildOutput(int)));
}

void EditScreen::ShowLine(int numLines) {
  QMessageBox* m = new QMessageBox();
  m->setText(QString::number(numLines));
  m->show();
}

void EditScreen::ShowFileSelected(const QModelIndex & index) {
  QString path = projectmodel->path(index);
  ShowFileSelected(path);
}

void EditScreen::ShowFileSelected(const QString &filename) {
  QFileInfo file(currentPath->filePath(filename));
  bool opened = false;
  int i;
  for (i = 0; i < tab->count(); i++) {
    if (file.fileName() == tab->tabText(i)) {
      opened = true;
      break;
    }
  }

  if(opened) {
    tab->setCurrentIndex(i);
  } else if ((QString("go yaml html").split(" ").contains(file.suffix()))) {
    QString absolutePath = file.absoluteFilePath();
    QFile f(absolutePath);

    if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
      CodeEditor* editor = new CodeEditor();
      tab->addTab(editor, file.fileName());
      tab->setTabToolTip(tab->count() - 1, absolutePath);
      editor->setPath(absolutePath);
      editor->setProjectPath(currentPath->path());
      QTextStream in(&f);
      editor->document()->setPlainText(in.readAll());
      editor->document()->setModified(false);
      f.close();
      connect(editor, SIGNAL(modificationChanged(bool)), this, SLOT(ShowChangeNote(bool)));
      connect(editor, SIGNAL(saveRequested()), this, SLOT(SaveFile()));

      if (file.suffix() == QString("go")) {
        Highlighter* highlighter = new Highlighter(editor->document());
        highlighter->document()->setModified(false);
      }

      watcher->addPath(absolutePath);
      tab->setCurrentIndex(tab->count() - 1);
    } else {
      QMessageBox::warning(this, "Warning", f.errorString(), QMessageBox::Cancel);
    }
  }
}

void EditScreen::setProject(const QString path) {
  QFile file(path);
  if (file.open(QIODevice::ReadOnly)) {
    QDomDocument document;
    if (document.setContent(&file)) {
      projectmodel = new ProjectManager(document, path, this);
      tree->setModel(projectmodel);
      tree->header()->hide();
      tree->header()->hideSection(1);
      tree->header()->hideSection(2);
      QFileInfo f(path);
      currentPath->setPath(f.path());
    }
  }

  sender()->deleteLater();
}

void EditScreen::ShowChangeNote(bool c) {
  int currentTab = tab->currentIndex();

  //show the note
  if (c) {
    tab->setTabIcon(currentTab, QIcon(":/icon/knewstuff.png"));
  } else {
    tab->setTabIcon(currentTab, QIcon());
  }
}

void EditScreen::CloseTab(int index) {
  CodeEditor* w = (CodeEditor*) tab->widget(index);

  if (w->document()->isModified()) {
    switch (QMessageBox::warning(this, "File not save", "Would you like to save it now?", QMessageBox::Save, QMessageBox::Discard)) {
    case QMessageBox::Save:
      SaveFile();
      goto removeit;
    case QMessageBox::Discard:
      goto removeit;
    default:
      return;
    }
  }

removeit:
  watcher->removePath(tab->tabToolTip(index));
  tab->removeTab(index);
  delete w;
}

void EditScreen::CloseAll() {
  if(run_process->state() == QProcess::Running)
    run_process->terminate();
  if(build_process->state() == QProcess::Running)
    build_process->terminate();
  while(tab->count()) {
    CloseTab(0);
  }
}

void EditScreen::SaveFile() {
  CodeEditor* editor = (CodeEditor*) tab->currentWidget();
  QString text = editor->toPlainText();
  QString path = tab->tabToolTip(tab->currentIndex());
  QFile f(path);

  if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
    watcher->removePath(path);
    f.write(text.toUtf8());
    f.close();
    watcher->addPath(path);
    tab->setTabIcon(tab->currentIndex(), QIcon());
  }

  editor->document()->setModified(false);
}

void EditScreen::ShowFileModifiedNode(const QString &path) {
  //cheack if delete/rename or content modified
  QFile f(path);

  if (f.exists()) {
    QMessageBox::warning(this, "Warning", "change", QMessageBox::Reset, QMessageBox::Ignore);
  } else {
    QMessageBox::warning(this, "Warning", "delete or rename", QMessageBox::Close, QMessageBox::Save);
  }
}

void EditScreen::AddFile() {
  if (tree->currentIndex().isValid()) {
    projectmodel->insertRows(projectmodel->rowCount(tree->currentIndex()), 1, tree->currentIndex());
    tree->update(tree->currentIndex());
  }
}

void EditScreen::RemoveFile() {
  QModelIndex current = tree->currentIndex();
  if (current.isValid()) {
    QModelIndex parent = current.parent();
    if(parent.isValid()) {
      projectmodel->removeRows(current.row(), 1, parent);
    }
  }
}

void EditScreen::RenameFile() {
  QModelIndex index = tree->currentIndex();
  if (index.isValid()) {
    bool ok;
    QString text = QInputDialog::getText(this, tr("Rename"),
                                        tr("New name:"), QLineEdit::Normal,
                                        projectmodel->data(index,
                                        Qt::DisplayRole).toString(), &ok);
    if(ok && !text.isEmpty()) {
      QFile f(currentPath->filePath(projectmodel->path(index)));
      if(f.rename(currentPath->filePath(text))){
        projectmodel->setData(index, text);
        projectmodel->updateConfig();
        tree->update(index);
      }
    }
  }
}

bool EditScreen::Format() {
  CodeEditor* editor = (CodeEditor*) tab->currentWidget();
  if(tab->count() == 0)
    return false;

  errlist->clean();
  if(!editor->format(errlist)) {
    output_tab->setCurrentIndex(0);
    if(output_tab->isHidden()) {
      bt_toggle_output->click();
    }
    return false;
  }
  SaveFile();
  return true;
}

void EditScreen::JumpTo(const QString &filename, int line) {
  ShowFileSelected(filename);
  CodeEditor* editor = (CodeEditor*) tab->currentWidget();
  QTextCursor cursor = editor->textCursor();
  cursor.setPosition(0);
  cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, line - 1);
  editor->setTextCursor(cursor);
}

bool EditScreen::Build() {
  if(!projectmodel)
    return false;
  if(projectmodel->kind() == ProjectManager::GAE) {
    QString appengine_path = s->value("general/appengine/path").toString();
    QStringList args;
    args.append("-dynamic=true");
    args.append("-unsafe=true");
    args.append(appengine_path.prepend("-goroot=").append("/goroot"));
    args.append(QString("-app_base=").append(currentPath->filePath("src")));

    QStringList files = projectmodel->codeFiles();
    QString name;
    foreach(name, files) {
      args.append(name.remove(0, 4));
    }

    build_process->start(appengine_path.append("/goroot/bin/go-app-builder"), args);
  }
  return false;
}

void EditScreen::Run() {
  if(!projectmodel)
    return;
  if(bt_run->isChecked()) {
    bt_run->setIcon(QIcon(":/icon/cancel.png"));
    bt_run->setToolTip("Cancel");
    run_output->setPlainText("");
    if(projectmodel->kind() == ProjectManager::GAE) {
      QStringList args;
      args.append(currentPath->filePath("src"));
      run_process->start(s->value("general/appengine/path").toString()
                         .append("/dev_appserver.py"), args);
    }
    output_tab->setCurrentIndex(2);
    if(output_tab->isHidden()) {
      bt_toggle_output->click();
    }
  } else {
    bt_run->setIcon(QIcon(":/icon/run.png"));
    bt_run->setToolTip("Run");
    run_process->terminate();
  }
}

void EditScreen::ShowRunOutput() {
  run_output->appendPlainText(QString::fromUtf8(run_process->readAllStandardOutput()));
}

void EditScreen::ResetRunButton(int) {
    bt_run->setIcon(QIcon(":/icon/run.png"));
    bt_run->setToolTip("Run");
    bt_run->setChecked(false);
}

void EditScreen::ShowBuildOutput(int exitCode) {
  QString output = QString::fromUtf8(build_process->readAllStandardOutput());
  build_output->setPlainText(output);
  if(exitCode == 1) {
    errlist->clean();
    QStringList error_part = output.split("\n", QString::SkipEmptyParts);
    QStringList error;
    QString file;
    int line;
    for(int i = 0; i < error_part.length() - 1; i++) {
      error = error_part.at(i).split(":", QString::SkipEmptyParts);
      if(error.length() > 2) {
        file = error.at(0);
        error.removeFirst();
        line = error.at(0).toInt();
        error.removeFirst();
        errlist->AddError(file, error.join(":"), line);
      }
    }
    output_tab->setCurrentIndex(0);
    if(output_tab->isHidden()) {
      bt_toggle_output->click();
    }
  }
}
